/*
 * Adara WebCam TimeLapse capture software.
 * Copyright (C) 2012 AdaraProjectTeam
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package adara.cli;

import java.io.File;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.log4j.Logger;

import adara.core.AdaraCore;
import adara.core.CoreException;
import adara.core.Device;
import adara.core.process.CountProcess;
import adara.core.process.TimeProcess;

/**
 * The class which read and parse the arguments provided.
 * @author Andrei Ermicioi (aka erani) <andrei.ermicioi@gmail.com>
 *
 */
public class CLIExecutor {
	
	private static final Logger logger = Logger.getLogger(CLIExecutor.class);
	
	private CommandLine cli;
	private Options options;
	
	private CLIExecutor(String argc[]) throws ParseException {
		options = this.factoryOptions();
		cli = this.factoryCommandLine(options, argc);
	}
	
	public static void execute(String argc[]) {
		CLIExecutor cmd = null;
		
		try {
			cmd = new CLIExecutor(argc);
		} catch (ParseException e) {
			logger.error("Error in reading the parameters", e);
		}
		
		String command = cmd.getCommand();
		
		try {
			CLIValidator.validate(command, cmd.getCommandLine());
		} catch (CLIException e) {
			logger.error(e.getMessage(), e);
			return;
		}
		
		cmd.execute(command);
	}
	
	private CommandLine factoryCommandLine(Options options, String[] argc) 
		throws ParseException {
		
		return new GnuParser().parse(options, argc);
	}
	
	private Options factoryOptions() {
		Option helpOption = new Option("h", "help", false, "Print the list of options");
		Option listOption = new Option("l", "list", false, "List available devices");
		Option infoOption = new Option("i", "info", false, "Show the information about device");
		Option captureOption = new Option("c", "capture", false, "Starts the capturing process");

		OptionBuilder.withArgName("directory");
		OptionBuilder.hasArg();
		OptionBuilder
				.withDescription("The directory where images will be stored");
		Option storageOption = OptionBuilder.create("s");
		storageOption.setLongOpt("storage");

		OptionBuilder.withArgName("device sequence");
		OptionBuilder.hasArg();
		OptionBuilder.withDescription("The device on which to capture");
		Option deviceOption = OptionBuilder.create("d");
		deviceOption.setLongOpt("device");

		OptionBuilder.withArgName("seconds");
		OptionBuilder.hasArg();
		OptionBuilder.withDescription("The amount of seconds between capture");
		Option gapOption = OptionBuilder.create("g");
		gapOption.setLongOpt("gap");
		
		OptionBuilder.withArgName("seconds");
		OptionBuilder.hasArg();
		OptionBuilder.withDescription(
				"The time in second for how long to capture images");
		Option timeOption = OptionBuilder.create("t");
		timeOption.setLongOpt("time");

		OptionBuilder.withArgName("count");
		OptionBuilder.hasArg();
		OptionBuilder
				.withDescription("The number of images to capture");
		Option countOption = OptionBuilder.create("k");
		countOption.setLongOpt("count");

		Options options = new Options();
		options.addOption(helpOption);
		options.addOption(listOption);
		options.addOption(gapOption);
		options.addOption(infoOption);
		options.addOption(captureOption);
		options.addOption(storageOption);
		options.addOption(deviceOption);
		options.addOption(timeOption);
		options.addOption(countOption);

		return options;
	}
	
	private void execute(String command) {
		if (command.equals(CLICommand.HELP)) {
			printHelp();
			return;
		}
		
		if (command.equals(CLICommand.LIST)) {
			printDevicesList();
			return;
		}
		
		if (command.equals(CLICommand.INFO)) {
			printDeviceInfo();
		}
		
		if (command.equals(CLICommand.CAPTURE)) {
			runCapture();
		}
	}
	
	private CommandLine getCommandLine() {
		return cli;
	}
	
	private String getCommand() {
		if (cli.hasOption(CLICommand.HELP)) {
			return CLICommand.HELP;
		}
		
		if (cli.hasOption(CLICommand.LIST)) {
			return CLICommand.LIST;
		}
		
		if (cli.hasOption(CLICommand.INFO)) {
			return CLICommand.INFO;
		}
		
		if (cli.hasOption(CLICommand.CAPTURE)) {
			return CLICommand.CAPTURE;
		}
		
		return CLICommand.HELP;
	}
	
	private void printHelp() {
		StringBuilder sb = new StringBuilder("adara ");
		sb.append("[-h] [-l] [-i -d <device>] ")
			.append("[-c -d <device> -g <seconds> -s <storage> ")
			.append("[-t <seconds>] [-k <count>]]");
		
		HelpFormatter hformater = new HelpFormatter();
		hformater.printHelp(sb.toString(), options);
	}
	
	private void printDevicesList() {
		List<Device> devices = null;
		
		try {
			devices = AdaraCore.getDeviceList();
		} catch (CoreException e) {
			logger.fatal("An exception occure on getting the device list", e);
			return;
		}
		
		if (devices.size() <= 0) {
			System.out.println("No devices found");
			return;
		}
		
		System.out.println("Device list");
		
		for (Device dev : devices) {
			printDeviceInfo(dev);
		}
	}
	
	private void printDeviceInfo() {
		Device dev = null;
		int seq = Integer.valueOf(cli.getOptionValue(CLIOption.DEVICE));
		
		try {
			dev = AdaraCore.getDeviceBySequence(seq);
		} catch (CoreException e) {
			logger.fatal("Could not obtain information on device", e);
			return;
		}
		
		System.out.println("Device info");
		printDeviceInfo(dev);
	}
	
	private void printDeviceInfo(Device dev) {
		String lineSeparator = System.getProperty("line.separator");
		StringBuilder sbuilder = new StringBuilder();
		sbuilder.append("------")
			.append(lineSeparator)
			.append("Seqence: ")
			.append(dev.getSequence())
			.append(lineSeparator)
			.append("Device ID: ")
			.append(dev.getDeviceId())
			.append(lineSeparator)
			.append("Device description: ")
			.append(dev.getDeviceDescription());
		
		System.out.println(sbuilder.toString());
	}
	
	private void runCapture() {
		int seq = Integer.valueOf(cli.getOptionValue(CLIOption.DEVICE));
		int gap = Integer.valueOf(cli.getOptionValue(CLIOption.GAP));
		File storage = new File(cli.getOptionValue(CLIOption.STORAGE));
		Device device = null;
		
		try {
			device = AdaraCore.getDeviceBySequence(seq);
		} catch (CoreException e) {
			logger.fatal("Could not obtain device by sequence number");
			return;
		}
		
		if (cli.hasOption(CLIOption.TIME)) {
			this.runTimeCapture(device, gap, storage);
		} else if (cli.hasOption(CLIOption.COUNT)) {
			this.runCountCapture(device, gap, storage);
		}
	}
	
	private void runTimeCapture(Device dev, int gap, File storage) {
		int time = Integer.valueOf(cli.getOptionValue(CLIOption.TIME));
		TimeProcess process = null;
		
		try {
			process = AdaraCore.factoryTimeProcess(dev, gap, storage);
		} catch (CoreException e) {
			logger.fatal("Could not factory process by sequence", e);
		}
		
		process.addObserver(new Observer() {
			
			@Override
			public void update(Observable caller, Object arg) {
				TimeProcess p = (TimeProcess) caller;
				System.out.println("Done: " + p.getProgressStatus() + "%");
			}
		});
		
		process.launch(time);
	}
	
	private void runCountCapture(Device dev, int gap, File storage) {
		int count = Integer.valueOf(cli.getOptionValue(CLIOption.COUNT));
		CountProcess process = null;
		
		try {
			process = AdaraCore.factoryCountProcess(dev, gap, storage);
		} catch (CoreException e) {
			logger.fatal("Could not factory process by sequence", e);
		}
		
		process.addObserver(new Observer() {
			
			@Override
			public void update(Observable caller, Object arg) {
				CountProcess p = (CountProcess) caller;
				System.out.println("Done: " + p.getProgressStatus() + "%");
			}
		});
		
		process.launch(count);
	}
	
	interface CLICommand {
		public static final String HELP = "help";
		public static final String LIST = "list";
		public static final String INFO = "info";
		public static final String CAPTURE = "capture";
	}
	
	interface CLIOption {
		public static final String DEVICE = "device";
		public static final String GAP = "gap";
		public static final String STORAGE = "storage";
		public static final String TIME = "time";
		public static final String COUNT = "count";
	}
}
